import copy
import uuid
import warnings
from typing import Iterator, Literal, Optional, Sequence
import numpy
import pandas
from pydantic import BaseModel, Field
from .tables import TableData, TableSeries
from .terminologies import BoreTypes, GeoMaterialProps

layers_depth: Any
materials_id: Any
materials: Any
profile_num: Any
water_depth: Any
profile_1d: Any

def sort_layer_numbers(layer_numbers: list, raise_on_invalid: bool = False) -> list[str]:
    """
    Sort layer numbers considering the format like "2", "2-1", "3", "4".
    The layer number should follow the format: '1', '1-1', '2-1-0', etc.
    Three numbers at most, `1` is equal to `1-0-0`, `1-1` is equal to `1-1-0`.
    
    Parameters
    ----------
    layer_numbers : list
        List of layer numbers (can be strings or numbers)
    
    raise_on_invalid : bool, default: False
        If True, raises ValueError on invalid layer numbers.
        If False, returns the original list converted to strings when invalid.
    
    Returns
    -------
    list[str]
        Sorted list of layer numbers as strings
    
    Raises
    ------
    ValueError
        If raise_on_invalid is True and invalid layer numbers are found.
    """
    ...

class Profile1D:
    """Define a 1D profile of geological layer."""
    def __init__(self, top: Any = None, x_coord: Any = None, y_coord: Any = None, layers_depth: Any = None, materials_id: Any = None, gwt_depth: Any = None, pnum: Any = None, samples_depth: Any = None, samples_id: Any = None, isamples_top_depth: Any = None, isamples_length: Any = None, isamples_id: Any = None) -> Any:
        """Initialize a Profile1D object."""
        ...
    @property
    def layers_thickness(self) -> Any:
        """Return the layer thickness of each layer."""
        ...
    @property
    def layers_elevation(self) -> Any:
        """Return the layer bottom elevation of each layer."""
        ...
    @property
    def layers_top_elevation(self) -> Any:
        """Return the layer top elevation of each layer."""
        ...
    @property
    def gwt_elevation(self) -> Any:
        ...
    @property
    def samples_elevation(self) -> Any:
        """Return the sample elevation of each sample."""
        ...
    @property
    def isamples_bottom_depth(self) -> Any:
        """Return the length of each interval samples."""
        ...
    @property
    def isamples_center_depth(self) -> Any:
        """Return the center depth of each interval sample."""
        ...
    @property
    def isamples_top_elevation(self) -> Any:
        """Return the  top elevation of each interval sample."""
        ...
    @property
    def isamples_bottom_elevation(self) -> Any:
        """Return the  bottom elevation of each interval sample."""
        ...
    @property
    def isamples_center_elevation(self) -> Any:
        """Return the center elevation of each interval sample."""
        ...
    def cut_profile(self, cut_elev: float, keep_below: bool = True, add_fill: bool = False, fill_id: int = ..., inplace: bool = False) -> Optional[Any]:
        """
        Cut a 1D profile to 2 profiles and get one of them.
        
        Parameters:
        -----------
        cut_elev : float, default: 0.0
            The elevation at where to cut the profile.
        
        keep_below : bool, default: True
            If it's True, the profile below the cut_elev will be returned.
        
        add_fill: bool, default: True
            If it's True, when the cut_elev is above the top of profile and keep below is True, fill will be added.
        
        fill_id : int, default: -1
            The material id for the fill.
        
        inplace : bool, default: False
            If it's true, the instance will be modified inplace, or a new instance will be returned.
        """
        ...
    def slice_profile(self, slice_top: float, slice_bottom: float, inplace: bool = False) -> Optional[Any]:
        """
        Get(slice) a section of a 1d profile between 2 elevations.
        
        Parameters
        ----------
        slice_top : float
            The top elevation of the slice.
        
        slice_bottom : float
            The bottom elevation of the slice.
        
        inplace : bool, default: False
            If it's true, the instance will be modified inplace, or a new instance will be returned.
        
        Note
        ----
        If slice top is above profile top or slice bottom is below profile bottom, no fill will be added.
        """
        ...
    def cal_samples_thickness(self) -> Any:
        """
        Calculate the characteristic thickness of each sample.
        
        Return
        ------
        dict[int, list[float]]: key is the id of the sample,
        value1 is the characteristic thickness of the sample,
        value2 is the top elevation of current represent layer,
        value3 is the bottom elevation of current represent layer.
        """
        ...

class MultiProfile1D:
    """Object for a list of Profile1D."""
    def __init__(self, profile_1ds: Any = None, profiles_table: Any = None, layers_table: Any = None, samples_table: Any = None, isamples_table: Any = None) -> Any:
        """
        Initialize a MultiProfile1D object.
        
        Parameters
        ----------
        profile_1ds: list[Profile1D], default: None
            List of Profile1D objects. If it's not None, the profile1d_table will be ignored.
        
        profiles_table: pd.DataFrame, default: None
            Data of profile_1d information with columns '["pnum", "top", "x_coord", "y_coord", "gwt_depth"]'
        
        layers_table: pd.DataFrame, default: None
            Data of layers information with columns '["pnum", "layers_depth", "materials_id"]'
        
        samples_table: pd.DataFrame, default: None
            Data of samples information with columns '["pnum", "samples_depth", "samples_id"]'
        
        isamples_table: pd.DataFrame, default: None
            Data of isamples information with columns '["pnum", "isamples_top_depth", "isamples_length", "isamples_id]'
        """
        ...
    def __iter__(self) -> Iterator[Profile1D]:
        """Iterate over MultiProfile1D by Profile1D object."""
        ...
    def __getitem__(self, key) -> Profile1D:
        ...
    def add_profile(self, profile: Profile1D) -> Any:
        """Add a profile in the data table."""
        ...
    def remove_profile(self, pnum: str) -> Any:
        """Remove a profile from the tables by pnum."""
        ...
    def get_profile(self, pnum: str) -> Any:
        """Get a single Profile by profile number."""
        ...

class MaterialTable(TableData):
    """Using TableData to define a material table containing all kinds of materials information."""
    def __init__(self, data: Any = None, *args, **kwargs):
        """
        Initialize MaterialTable with material_id as index.
        
        Parameters
        ----------
        data : pd.DataFrame | dict | None, default None
            Input data. If DataFrame, must contain 'material_id' column with unique values.
            If dict, must contain 'material_id' as one of the keys with unique values.
        
        Note
        ----
        The material_id will be set as the index of the MaterialTable.
        
        Raises
        ------
        ValueError
            If material_id of column or index is missing or contains duplicate values.
        """
        ...
    def get_material(self, material_id: int) -> Any:
        """Return the material properties by specified material ID."""
        ...
    def add_material(self, material: dict[str, Any]) -> Any:
        """
        Add a row to the material table by a dictionary.
        
        Parameters
        ----------
        material : dict[str, str | float | int | None]
            Dictionary containing material properties. May include 'material_id' key.
            If material_id is not specified, it will be assigned automatically starting from 0.
            Other keys should match existing table columns.
        
        Raises
        ------
        ValueError
            If material_id already exists, or if invalid columns are provided.
        """
        ...
    def delete_material(self, material_id: int) -> Any:
        """Delete a row from the material table by specified material ID."""
        ...
    def sort_by_layer_number(self, inplace: bool = True, reset_material_id: bool = True) -> Optional[Any]:
        """
        Sort the material table by layer number in ascending order.
        The layer number should follow the format: '1', '1-1', '2-1-0', etc.
        Three numbers at most, `1` is equal to `1-0-0`, `1-1` is equal to `1-1-0`.
        
        Parameters
        ----------
        inplace : bool, default: True
            If True, the material table will be sorted in place.
            If False, a new sorted material table will be returned.
        
        reset_material_id : bool, default: True
            If True, the material_id will be reset to the ascending order of layer number which means the material_id will start from 0 at the first row after sorting.
        """
        ...
    def get_material_id(self, layer_number: Any = None, material_name: Any = None) -> int:
        """
        Get the material_id by layer_number or material_name.
        
        Parameters
        ----------
        layer_number : str | None, default: None
            The layer number to get the material_id.
        
        material_name : str | None, default: None
            The material name to get the material_id.
        
        Note
        ----
        If layer_number is provided, the material_name will be ignored.
        """
        ...
    def convert_to_table_data(self) -> TableData:
        """
        Convert the material table to a table data.
        The material_id index will be removed and a new column named 'material_id' will be added.
        """
        ...
    def copy(self, deep = True):
        """Override copy to preserve MaterialTable type instead of returning TableData"""
        ...
    def __getitem__(self, key):
        """Override __getitem__ to preserve MaterialTable type when appropriate"""
        ...
    def reset_index(self, level = None, drop = False, inplace = False, col_level = 0, col_fill = ""):
        """Override reset_index to preserve MaterialTable type"""
        ...

class MaterialTableCollection:
    """A collection of material tables."""
    def __init__(self, data: Any = None, name: Any = None, title: Any = None, description: Any = None):
        ...
    def add_table(self, table: MaterialTable) -> Any:
        """Add a material table to the collection."""
        ...
    def get_table(self, key: str) -> MaterialTable:
        """Get a material table by name."""
        ...
    def __getitem__(self, key: str) -> MaterialTable:
        """Get a material table by name."""
        ...
    def __len__(self) -> int:
        """Get the number of material tables in the collection."""
        ...
    def __iter__(self) -> Iterator[MaterialTable]:
        """Iterate over the material tables in the collection."""
        ...
    def __contains__(self, key: str) -> bool:
        """Check if a material table is in the collection."""
        ...

class ProjectInfo(BaseModel):
    """项目信息"""
    company_name: str | None
    project_name: str | None
    project_number: str | None

class SectionBore(BaseModel):
    """钻孔"""
    bore_num: str | None
    bore_type: BoreTypes | None
    x_coord: float | None
    x: float | None
    y: float | None
    top: float | None
    bore_diameter: float | None
    bore_starting_date: str | None
    bore_completion_date: str | None
    steady_water_depth: float | None
    water_level_measure_date: str | None
    drawing_scale: int | None
    elevations: Sequence[float] | None
    depths: Sequence[float] | None
    materials: Sequence[int] | None
    layers_num: Sequence[str] | None
    distance_to: float | None
    sample_num: list[str] | None
    sample_depths: Sequence[float] | None
    sample_lengths: Sequence[float] | None
    sample_types: Sequence[int] | None
    spt_depths: Sequence[float] | None
    spt_lengths: Sequence[float] | None
    spt_numbers: Sequence[int] | None
    dpt_top_depths: Sequence[float] | None
    dpt_bot_depths: Sequence[float] | None
    dpt_numbers: Sequence[float] | None
    cpt_bot_depths: Sequence[float] | None
    cpt_tip_forces: Sequence[float] | None
    cpt_side_forces: Sequence[float] | None
    f_bot_elev_bore: float | None
    wave_velocity_depths: Sequence[float] | None
    transverse_wave_velocitys: Sequence[float] | None
    longitudinal_wave_velocitys: Sequence[float] | None
    collapsibility_top_depths: Sequence[float] | None
    collapsibility_lengths: Sequence[float] | None
    collapsibility_coes: Sequence[float] | None
    gravity_collapsibility_coes: Sequence[float] | None

class SectionInterface(BaseModel):
    """地质剖面图上地层线信息"""
    coords: Sequence[Any]
    material: int
    layer: int
    virtual_interface: bool

class SectionLine(BaseModel):
    name: str | None
    bores: list[str] | None

class SectionHatch(BaseModel):
    """地质剖面图填充信息"""
    coords: Sequence[Any]
    material: int
    layer: int

class SectionMaterial(BaseModel):
    material_name: str | None
    layer_num: str | None
    hatch: str | None
    color: tuple[int, int, int] | None
    geo_age: str | None
    geo_origin: str | None
    weathering_degree: str | None
    layer_description: str | None
    bearing_capacity: float | None
    pile_side_friction: float | None

class BoreForPlanDraw(BaseModel):
    """钻孔平面布置图需要的信息"""
    project_infos: ProjectInfo | None
    bores: list[SectionBore] | None
    scection_lines: list[SectionLine] | None
    y_direction: Literal[Any, Any] | None

class BoreForCadDraw(BaseModel):
    """钻孔柱状图需要的信息"""
    project_infos: ProjectInfo | None
    bores: list[SectionBore] | None
    materials: dict[int, SectionMaterial] | None

class SectionForCadDraw(BaseModel):
    """地质剖面图需要的信息"""
    bounds: Sequence[float] | None
    bores: list[SectionBore] | None
    interfaces: list[SectionInterface] | None
    hatches: list[SectionHatch] | None
    materials: dict[int, SectionMaterial] | None
    section_line: Sequence[Any] | None
    section_num: str | None

class Secction3D(BaseModel):
    pass
